﻿using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using ProjectTemplate.Common.Response;
using ProjectTemplate.Identity.Authorization;
using ProjectTemplate.Identity.Data;
using ProjectTemplate.Identity.Models;
using ProjectTemplate.Identity.Services;
using ProjectTemplate.IServices;
using ProjectTemplate.Model.ViewModels.Account;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

namespace ProjectTemplate.Services
{
    public class AccountService : IAccountService
    {
        private readonly SignInManager<User> _signInManager;
        private readonly UserManager<User> _userManager;
        private readonly ILogger<AccountService> _logger;
        private readonly JwtOptions _options;
        private readonly RoleManager<Role> _roleManager;


        public AccountService(
            SignInManager<User> signInManager,
            UserManager<User> userManager,
            ILogger<AccountService> logger,
            IOptions<JwtOptions> options
,
            RoleManager<Role> roleManager)
        {
            _signInManager = signInManager;
            _userManager = userManager;
            _logger = logger;

            this._options = options.Value;
            _roleManager = roleManager;
        }

        public async Task<Response<LoginViewModel>> Login(LoginDTO login)
        {
            var signResult = await _signInManager.PasswordSignInAsync(login.Email, login.Password, false, false);
            if (!signResult.Succeeded)
                return ResultBuilder.FailResult(new LoginViewModel(), "Email or Password " + signResult.ToString());

            var user = await _userManager.FindByEmailAsync(login.Email);

            _logger.LogInformation("User logged in.");

            var token = await GenerateToken(user);

            return ResultBuilder.SuccessResult(token);

        }

        public async Task<Response<bool>> Register(RegisterDTO dto)
        {
            bool roleExists = await _roleManager.RoleExistsAsync("admin");
            if (!roleExists)
            {
                Role role = new Role { Name = "Admin" };
                var r = await _roleManager.CreateAsync(role);
                if (!r.Succeeded)
                {
                    return ResultBuilder.FailResult(false, "角色创建失败");
                }
            }

            //Add User
            var user = new User { UserName = dto.Email, Email = dto.Email, PhoneNumber = dto.PhoneNumber };

            var identityResult = await _userManager.CreateAsync(user, dto.Password);
            if (!identityResult.Succeeded) return ResultBuilder.FailResult(false, identityResult.ToString());

            //Add UserRole
            identityResult = await _userManager.AddToRoleAsync(user, "admin");
            if (!identityResult.Succeeded) return ResultBuilder.FailResult(false, identityResult.ToString());

            // Add UserClaims
            var userClaims = new List<Claim>
            {
                new Claim("Category_Write", "Write"),
                new Claim("Category_Remove", "Remove"),
            };
            await _userManager.AddClaimsAsync(user, userClaims);

            _logger.LogInformation("User created a new account with password.");

            return ResultBuilder.SuccessResult(true, identityResult.ToString());

        }

        private async Task<LoginViewModel> GenerateToken(User user)
        {
            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.UserName)
            };

            var roles = await _userManager.GetRolesAsync(user);
            foreach (string role in roles)
            {
                claims.Add(new Claim(ClaimTypes.Role, role));
            }

            DateTime expires = DateTime.Now.AddSeconds(_options.ExpireSeconds);

            byte[] keyBytes = Encoding.UTF8.GetBytes(_options.SigningKey);

            var secKey = new SymmetricSecurityKey(keyBytes);

            var credentials = new SigningCredentials(secKey, SecurityAlgorithms.HmacSha256Signature);

            var tokenDescriptor = new JwtSecurityToken(expires: expires, signingCredentials: credentials, claims: claims);


            // Generate access token
            var jwtToken = new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);

            return new LoginViewModel
            {
                AccessToken = jwtToken,
                Email = user.Email,
                PhoneNumber = user.PhoneNumber,
                UserName = user.UserName
            };
        }
    }
}
